#pragma once

#include <llvm/Support/raw_ostream.h>
#include <llvm/ADT/SmallString.h>
#include <llvm/ADT/ArrayRef.h>
#include <clang/AST/APValue.h>
#include <clang/AST/Type.h>
#include <clang/AST/ASTContext.h>

namespace CppSharp { namespace CppParser {
    struct APValuePrinter
    {
        clang::ASTContext& ctx;
        clang::raw_ostream& out;

        bool Print(clang::APValue& value, clang::QualType type)
        {
            using namespace clang;

            switch (value.getKind())
            {
                case APValue::Int:
                    if (type->isBooleanType())
                        out << (value.getInt().getBoolValue() ? "true" : "false");
                    else
                        out << value.getInt();
                    return true;
                case APValue::Float:
                {
                    SmallString<50> str;
                    value.getFloat().toString(str);
                    out << str.str().str();
                    return true;
                }
                case APValue::Vector:
                {
                    out << '{';
                    const auto elementType = type->castAs<clang::VectorType>()->getElementType();
                    if (!Print(value.getVectorElt(0), elementType))
                        return false;
                    for (unsigned i = 1; i != value.getVectorLength(); ++i)
                    {
                        out << ", ";
                        Print(value.getVectorElt(i), elementType);
                    }
                    out << '}';
                    return true;
                }
                case APValue::Array:
                {
                    const auto* arrayType = ctx.getAsArrayType(type);
                    const auto elementType = arrayType->getElementType();
                    out << '{';
                    if (unsigned n = value.getArrayInitializedElts())
                    {
                        if (!Print(value.getArrayInitializedElt(0), elementType))
                            return false;
                        for (unsigned i = 1; i != n; ++i)
                        {
                            out << ", ";
                            Print(value.getArrayInitializedElt(i), elementType);
                        }
                    }
                    out << '}';
                    return true;
                }
                case APValue::LValue:
                {
                    auto base = value.getLValueBase();

                    if (const ValueDecl* VD = base.dyn_cast<const ValueDecl*>())
                    {
                        out << *VD;
                        return true;
                    }
                    else if (TypeInfoLValue TI = base.dyn_cast<TypeInfoLValue>())
                        return false;
                    else if (DynamicAllocLValue DA = base.dyn_cast<DynamicAllocLValue>())
                        return false;
                    else
                    {
                        base.get<const clang::Expr*>()->printPretty(out, nullptr, ctx.getPrintingPolicy());
                        return true;
                    }
                }
            }

            return false;
        }
    };
}} // namespace CppSharp::CppParser